
namespace Behemoth.SharePoint.Data {

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;

    using System.IO;

    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;

    using Behemoth.SharePoint.Utilities;

	public class SPHierarchicalListDataSource : HierarchicalDataSourceControl {

		public SPHierarchicalListDataSource() : base() { }

		private SPHierarchicalListDataSourceView _view = null;
		public SPHierarchicalListDataSourceView View {
			get { return _view; }
		}

		private IEnumerable _items;
		public IEnumerable Items {
			get { return _items; }
			set { _items = value; }
		}

		private string _parentFieldName;
		public string ParentFieldName {
			get { return _parentFieldName; }
			set { _parentFieldName = value; }
		}

		private string _childFieldName;
		public string ChildFieldName {
			get { return _childFieldName; }
			set { _childFieldName = value; }
		}

		private string _displayFieldName;
		public string DisplayFieldName {
			get { return _displayFieldName; }
			set { _displayFieldName = value; }
		}

		protected override HierarchicalDataSourceView GetHierarchicalView( string viewPath ) {
			if (null == this._view)
				this._view = new SPHierarchicalListDataSourceView( viewPath );
			// ensure we have copied the properties to the view
			this._view.ChildFieldName = this.ChildFieldName;
			this._view.ParentFieldName = this.ParentFieldName;
			this._view.DisplayFieldName = this.DisplayFieldName;
			this._view.Items = this.Items;
			TestDataBind();
			return this._view;
		}

		protected override ControlCollection CreateControlCollection() {
			return new ControlCollection( this );
		}

		/// <summary>
		/// Throws an exception if there is a state where the databind will break.
		/// </summary>
		public void TestDataBind() {
			if (this._items == null)
				throw new ArgumentNullException( "_items", "The Items collection can not be null." );
			foreach (object o in this._items) {
				TestItem( o );
			}
		}
		private void TestItem( object itemOrContainer ) {
			if (itemOrContainer == null)
				return;
			SPListItem item = GetListItem( itemOrContainer );
			TestField( item, this.ParentFieldName );
			TestField( item, this.ChildFieldName );
			TestField( item, this.DisplayFieldName );
		}

		#region Static Helpers

		public static void TestField( SPListItem item, string fieldName ) {
			if (!item.Fields.FieldExists( fieldName ))
				throw new IndexOutOfRangeException( string.Format( "Child field name '{0}' was not returned by the data source.", fieldName ) );
		}

		/// <summary>
		/// Gets the SPListItem from a SPDataSourceViewResultItem
		/// or passes through an SPListItem object.
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static SPListItem GetListItem( object o ) {
			SPListItem item = null;
			if (o.GetType() == typeof( SPListItem )) {
				item = o as SPListItem;
			} else if (o.GetType() == typeof( SPDataSourceViewResultItem )) {
				SPDataSourceViewResultItem ri = (SPDataSourceViewResultItem)o;
				item = ri.ResultItem as SPListItem;
			}
			return item;
		}

		#endregion

	} // class

	public class SPHierarchicalListDataSourceView : HierarchicalDataSourceView {

		public SPHierarchicalListDataSourceView( string viewPath ) {
			// TODO what to do with viewPath???
		}

		private string _parentFieldName;
		public string ParentFieldName {
			get { return _parentFieldName; }
			set { _parentFieldName = value; }
		}

		private string _childFieldName;
		public string ChildFieldName {
			get { return _childFieldName; }
			set { _childFieldName = value; }
		}

		private string _displayFieldName;
		public string DisplayFieldName {
			get { return _displayFieldName; }
			set { _displayFieldName = value; }
		}

		private IEnumerable _items;
		public IEnumerable Items {
			get { return _items; }
			set { _items = value; }
		}

		public override IHierarchicalEnumerable Select() {
			if (_items == null)
				throw new ArgumentNullException( "_items", "This collection can not be null." );
			SPHierarchicalListEnumerable spEnum = new SPHierarchicalListEnumerable( this.ParentFieldName, this.ChildFieldName, this.DisplayFieldName );
			foreach (object o in this._items) {
				SPListItem item = SPHierarchicalListDataSource.GetListItem( o );
				if (item != null) {
					SPHierarchicalListData data = new SPHierarchicalListData( item, spEnum );
					spEnum.Add( data );
				}
			}
			return spEnum;
		}



	} // class

	/// <summary>
	/// A generic Hierarchical Enumerable based on an ArrayList
	/// </summary>
	public class ArrayListHierarchicalEnumerable : ArrayList, IHierarchicalEnumerable {

		public ArrayListHierarchicalEnumerable() : base() { }

		public IHierarchyData GetHierarchyData( object enumeratedItem ) {
			return enumeratedItem as IHierarchyData;
		}

	}

	public class SPHierarchicalListEnumerable : ArrayListHierarchicalEnumerable {

		public SPHierarchicalListEnumerable( SPHierarchicalListEnumerable copyProps )
			: base() {
			_parentFieldName = copyProps.ParentFieldName;
			_childFieldName = copyProps.ChildFieldName;
			_displayFieldName = copyProps.DisplayFieldName;
		}

		public SPHierarchicalListEnumerable(string parentField, string childField, string displayField) : base() {
			_parentFieldName = parentField;
			_childFieldName = childField;
			_displayFieldName = displayField;
		}

		private string _parentFieldName;
		public string ParentFieldName {
			get {
				return _parentFieldName;
			}
		}

		private string _childFieldName;
		public string ChildFieldName {
			get {
				return _childFieldName;
			}
		}

		private string _displayFieldName;
		public string DisplayFieldName {
			get {
				return _displayFieldName;
			}
		}

	}

	public class SPHierarchicalListData : IHierarchyData {

		public const string PathDelimiter = "/";

		private SPListItem _item = null;
		public SPListItem ListItem {
			get { return this._item; }
		}
		public object Item {
			get { return this._item; }
		}

		private SPHierarchicalListEnumerable _collection = null;

		public SPHierarchicalListData( SPListItem item, SPHierarchicalListEnumerable collection ) {
			this._item = item;
			this._collection = collection;
		}

		public override string ToString() {
			// TODO ummm error checking here?!
			object o = this._item[this._collection.DisplayFieldName];
			return o.ToString();
		}

		public bool HasChildren {
			get {
				return (((SPHierarchicalListEnumerable)GetChildren()).Count > 0);
			}
		}

		public string Path {
			get {
				string path = string.Empty;
				SPHierarchicalListData data = this;
				while (data != null) {
					// add the child node to the path
					if (!string.IsNullOrEmpty( path ))
						path = PathDelimiter + path;
					path = data.ChildField.ToString() + path;
					// attempt to get the parent and go on to the next step
					data = (SPHierarchicalListData)data.GetParent();
					// TODO if we cast this differently we might be able to have hierarchies with different kinds of objects in them...
				}
				return path;
			}
		}

		public string Type {
			get { return "SPListItem"; }
		}
		public string DisplayName {
			get { return ListItem.DisplayName; }
		}
		public int ID {
			get { return ListItem.ID; }
		}
		public string Name {
			get { return ListItem.Name; }
		}
		public Guid UniqueId {
			get { return ListItem.UniqueId; }
		}
		public string Title {
			get { return ListItem.Title; }
		}
		public string UrlTemplate {
			get { return ListItem.Url; }
		}
		public string UrlDisplay {
			get {
				return ListItem.ParentList.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID=" + ListItem.ID;
			}
		}
		public string UrlEdit {
			get {
				return ListItem.ParentList.Forms[PAGETYPE.PAGE_EDITFORM].ServerRelativeUrl + "?ID=" + ListItem.ID;
			}
		}

		private bool IsParent(object compareTo) {
			if (compareTo.GetType() == typeof( SPHierarchicalListData )) {
				SPHierarchicalListData compateData = (SPHierarchicalListData)compareTo;
				//SPListItem compareItem = compateData.Item;
				object thisParentField = this.ParentField;
				object compareChildField = compateData.ChildField;
				return (thisParentField == compareChildField); // TODO this may not work
				// TODO test for FlexLookupFields compared to a "real" fieldName
			}
			return false;
		}

		public object ParentField {
			get {
				string parentName = this._collection.ParentFieldName;
				if (this._item == null)
					throw new ArgumentNullException( "_item", "The Items collection can not be null." );
				SPField field = this._item.Fields.GetFieldEx( parentName );
				//if (field == null)
				//	throw new IndexOutOfRangeException(string.Format("Parent field name '{0}' was not returned by the data source.", parentName));
				return this._item[parentName];
			}
		}

		public object ChildField {
			get {
				string childName = this._collection.ChildFieldName;
				if (this._item == null)
					throw new ArgumentNullException("_item", "The Items collection can not be null.");
				SPField field = this._item.Fields.GetFieldEx( childName );
				//if (field == null)
				//	throw new IndexOutOfRangeException( string.Format( "Child field name '{0}' was not returned by the data source.", childName ) );
				return this._item[childName];
			}
		}

		private IHierarchicalEnumerable _children;
		public IHierarchicalEnumerable GetChildren() {
			if (_children == null) {
				SPHierarchicalListEnumerable children = new SPHierarchicalListEnumerable( this._collection );
				foreach (SPHierarchicalListData data in this._collection) {
					if (this.IsParent( data )) { // a match, add it
						children.Add( data );
					}
				}
				_children = children;
			}
			return _children;
		}

		public IHierarchyData GetParent() {
			foreach (SPHierarchicalListData data in this._collection) {
				if (data.IsParent( this )) { // a match, add it
					return data; 
				}
			}
			return null;
		}

	} // class

} // namespace
